import json
from calendar import timegm
from dataclasses import dataclass
from datetime import timedelta, datetime

from fastapi import Depends, APIRouter, Form, Request
from fastapi.security import OAuth2PasswordRequestForm

from rosa.common_apps.auth.views.auth_types import UserInfoResponse, SystemTokenBody
from rosa.love_is_blue import HTTPTools
from rosa.love_is_blue.types import BaseResponse, SiteException, RecordStatus, generate_response_model
from rosa.scarborough_fair import settings, StatusMap, logger, GenericBaseResponse
from rosa.scarborough_fair.depends.authorization.authorization import create_access_token, signature_authentication, optional_signature_authentication, permission_handler
from rosa.scarborough_fair.depends.authorization.types import PayloadDataUserInfo, PayloadData, TokenType, Token
from rosa.scarborough_fair.depends.cache.cache import CacheSystem, cache
from rosa.utils import gen_random_key_string


class LoginHandler:
    functions = dict()
    update_user_login_info = None

    def register(self, name: str, func: callable):
        self.functions[name] = func

    def register_update_login_info(self, func: callable):
        self.update_user_login_info = func


login_handler = LoginHandler()


@dataclass
class UserData:
    id: int = 0
    name: str = ""
    email: str = ""
    avatar: str = ""
    username: str = ""
    superuser: bool = False
    status: RecordStatus = RecordStatus.ACTIVE


router = APIRouter()

IdentifyInvalid = generate_response_model("IdentifyInvalid", StatusMap.IDENTIFY_INVALID)
UserNotActive = generate_response_model("UserNotActive", StatusMap.USER_NOT_ACTIVE)
LoginFailed = generate_response_model("LoginFailed", StatusMap.LOGIN_FAILED)

login_responses = {
    StatusMap.IDENTIFY_INVALID.http_code: {
        "model": IdentifyInvalid,
        "description": "登录信息错误",
        "content": {
            "application/json": {
                "example": {
                    "code": StatusMap.IDENTIFY_INVALID.code,
                    "success": StatusMap.IDENTIFY_INVALID.success,
                    "message": StatusMap.IDENTIFY_INVALID.message,
                    "data": {}
                }
            }
        }
    },
    StatusMap.LOGIN_FAILED.http_code: {
        "model": LoginFailed,
        "description": "登录失败，服务器错误",
        "content": {
            "application/json": {
                "example": {
                    "code": StatusMap.LOGIN_FAILED.code,
                    "success": StatusMap.LOGIN_FAILED.success,
                    "message": StatusMap.LOGIN_FAILED.message,
                    "data": {}
                }
            }
        }
    },
    StatusMap.USER_NOT_ACTIVE.http_code: {
        "model": UserNotActive,
        "description": "用户状态异常",
        "content": {
            "application/json": {
                "example": {
                    "code": StatusMap.USER_NOT_ACTIVE.code,
                    "success": StatusMap.USER_NOT_ACTIVE.success,
                    "message": StatusMap.USER_NOT_ACTIVE.message,
                    "data": {}
                }
            }
        }
    },
}


class OAuth2RequestForm(OAuth2PasswordRequestForm):
    def __init__(
            self,
            grant_type: str = Form(default=None, regex="password"),
            username: str = Form(),
            password: str = Form(),
            scope: str = Form(default=""),
            client_id: str | None = Form(default=None),
            client_secret: str | None = Form(default=None),
            remember: bool = Form(default=False)
    ):
        super().__init__(
            grant_type=grant_type,
            username=username,
            password=password,
            scope=scope,
            client_id=client_id,
            client_secret=client_secret
        )
        self.remember = remember


auth_http_tool = HTTPTools(backend_base_url=settings.AUTH_URL.strip("/") if settings.AUTH_URL.startswith("http") else f"http://127.0.0.1/{settings.AUTH_URL.strip('/')}")


async def get_permissions_by_user_id(user_id: int, token_type: str = "local", expire: int = 0):
    user_access_profile = await permission_handler.exec_handler(token_type, True, user_id, token_type)
    if user_access_profile.permissions:
        await permission_handler.save(user_id, token_type, user_access_profile, expire)


async def get_user_info_by_username(username: str, token_type: str = "local") -> dict:
    items, _, _ = await auth_http_tool.get_all_handler("获取用户信息", settings.AUTH_USERNAME_URL, filters={"username": username, "type": token_type})
    if items:
        return items[0]
    return dict()


async def get_user_info_by_password(username: str, password: str, token_type: str = 'local') -> dict:
    data = await auth_http_tool.create_handler("获取用户信息", settings.AUTH_PASSWORD_URL, {"username": username, "password": password, "type": token_type})
    return data


async def get_user_info(username: str, password: str = None, token_type: str = "local", request_id: str = "", is_refresh: bool = False) -> UserData | None:
    """
    获取用户信息
    :param username: 用户名
    :param password: 密码
    :param token_type: token类型，目前支持local、refresh和other
    :param request_id: 请求id
    :param is_refresh: 是否是刷新token时调用
    :return:
    """
    user_data = None
    try:
        # 如果是token认证，则验证用户名、密码是否正确
        # 如果是刷新token，则直接生成token信息
        if is_refresh:
            data = await get_user_info_by_username(username, token_type)
        else:
            data = await get_user_info_by_password(username, password, token_type)
        if data:
            user_data = UserData(
                id=data.get("id"),
                name=data.get("name"),
                email=data.get("email"),
                username=data.get("username"),
                superuser=data.get("superuser"),
                status=data.get("status")
            )
    except Exception as error:
        logger.error(f"[{request_id}]: 获取用户信息失败，错误原因为：{error}")
    return user_data


async def generate_token(
        username: str,
        password: str = None,
        remember: bool = False,
        scopes: str = "",
        token_type: str = "local",
        request_id: str = "",
        login_ip: str = "",
        is_refresh: bool = False,
) -> tuple[str, str]:
    """
    生成token
    :param username: 用户名
    :param password: 密码
    :param remember: 是否记住密码
    :param scopes: 权限范围
    :param token_type: token类型，目前支持local和other
    :param request_id: 请求id
    :param login_ip: 登录ip
    :param is_refresh: 是否是刷新token
    :return:
    """
    if settings.ENV == "product":
        # TODO 补齐获取权限的功能
        scopes = ""

    user = await get_user_info(username, password, token_type, request_id, is_refresh)

    if user is None:
        response = BaseResponse(code=StatusMap.IDENTIFY_INVALID.code, message=StatusMap.IDENTIFY_INVALID.message, success=StatusMap.IDENTIFY_INVALID.success)
        raise SiteException(status_code=StatusMap.IDENTIFY_INVALID.http_code, response=response)

    # 未激活状态报错
    if user.status != RecordStatus.ACTIVE.value:
        response = BaseResponse(code=StatusMap.USER_NOT_ACTIVE.code, message=StatusMap.USER_NOT_ACTIVE.message, success=StatusMap.USER_NOT_ACTIVE.success)
        raise SiteException(status_code=StatusMap.USER_NOT_ACTIVE.http_code, response=response)

    # 获取用户基本信息
    data = PayloadDataUserInfo(
        id=user.id,
        name=user.name,
        email=user.email,
        avatar=user.avatar,
        username=user.username,
        superuser=user.superuser,
        remember=remember,
        type=token_type
    )

    # 判断过期时间是否设置,如果设置了且两倍的过期时间大于1周,则过期时间设置为两倍的设置时间
    if settings.ACCESS_TOKEN_EXPIRE_MINUTES:
        # 两倍的过期时间
        double_access_token_expires_delta = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES * 2)

        if remember or is_refresh:
            # 记住密码或者刷新token时设置过期时间，取固定过期时间与两倍token过期时间最大值
            # 过期时间最小为1周
            expires_delta = timedelta(weeks=1)
            if expires_delta < double_access_token_expires_delta:
                expires_delta = double_access_token_expires_delta
        else:
            # 不记住密码时设置过期时间，取固定过期时间与两倍token过期时间最小值，最小值为1天
            expires_delta = timedelta(days=1)
            if expires_delta > double_access_token_expires_delta:
                expires_delta = double_access_token_expires_delta
    else:
        expires_delta = timedelta(days=1)

    scopes = scopes.split()
    access_token = await create_access_token(payload=PayloadData(scopes=scopes, data=data, aud=user.username))
    # 如果是刷新token并且不记住密码，则刷新token为空
    # 如果是刷新token并且记住密码，或者是access token，则生成refresh token
    if is_refresh and not remember:
        refresh_token = ""
    else:
        refresh_token = await create_access_token(
            payload=PayloadData(scopes=scopes, data=data, aud=user.username),
            expires_delta=expires_delta,
            subject=TokenType.REFRESH_TOKEN
        )
    # 获取权限信息和菜单，然后存储到缓存中
    await get_permissions_by_user_id(user.id, token_type)

    if not is_refresh:
        # 用户保存登录ip和登录时间
        update_user_login_info = login_handler.update_user_login_info
        if update_user_login_info:
            last_login_ip = login_ip
            last_login_time = datetime.now()
            await update_user_login_info(user.id, last_login_ip, last_login_time)

    return access_token, refresh_token


async def login(username: str, password: str, scope: str, remember: bool, request_id: str, login_ip: str, **kwargs) -> Token:
    """
    OAUTH2方式的token获取方式，返回格式仅为token格式
    \f
    :param username: 用户名
    :param password: 密码
    :param scope: 权限范围
    :param remember: 是否记住密码
    :param request_id: 请求id
    :param login_ip: 登录ip
    :param kwargs: 其他参数
    :return: token
    """
    response_data = Token()

    # 获取token
    access_token, refresh_token = await generate_token(username, password, remember, scope, "local", request_id, login_ip)

    response_data.access_token = access_token
    response_data.refresh_token = refresh_token
    return response_data


login_handler.register("password", login)


@router.post("/login", response_model=Token, summary="登录接口，获取token")
async def login(
        request: Request,
        username: str = Form(default=""),
        password: str = Form(default=""),
        grant_type: str = Form(default="password"),
        scope: str = Form(default=""),
        client_id: str = Form(default=""),
        client_secret: str = Form(default=""),
        remember: bool = Form(default=False),
        token: str = Form(default=""),
        user_info_url: str = Form(default=""),
        request_id: str = ''
):
    """
    登录接口，获取token
    \f
    :param request:
    :param username: 用户名
    :param password: 密码
    :param grant_type: 登陆方式，目前支持 "password" 和 "saas"
    :param scope: 权限范围
    :param client_id:
    :param client_secret:
    :param remember: 是否记住密码
    :param token: token
    :param user_info_url: 获取用户信息的url，通过其他网站直接登陆时通过此url验证
    :param request_id:
    :return:
    """
    if not request_id:
        request_id = gen_random_key_string()
    logger.info(f"登录接口，获取token，请求的ip为：{request.client.host}")
    # TODO 未验证转发情况的客户端ip
    login_ip = request.client.host
    logger.info(f"[{request_id}]: 登录接口，获取token，请求的ip为：{login_ip}")

    handler = login_handler.functions.get(grant_type)
    if not handler:
        response = BaseResponse(code=StatusMap.INVALID_GRANT_TYPE.code, message=StatusMap.INVALID_GRANT_TYPE.message, success=StatusMap.INVALID_GRANT_TYPE.success)
        raise SiteException(status_code=StatusMap.INVALID_GRANT_TYPE.http_code, response=response)

    try:
        response = await handler(
            username=username,
            password=password,
            scope=scope,
            remember=remember,
            request_id=request_id,
            client_id=client_id,
            client_secret=client_secret,
            token=token,
            user_info_url=user_info_url,
            login_ip=login_ip
        )
        return response
    except SiteException as error:
        raise error
    except Exception as error:
        logger.error(f"[{request_id}]: 登录接口，获取token，错误原因为：{error}")
        response = BaseResponse(code=StatusMap.LOGIN_FAILED.code, message=str(error), success=False)
        raise SiteException(status_code=StatusMap.LOGIN_FAILED.http_code, response=response)


@router.post(
    "/refresh",
    response_model=Token,
    summary="登录接口，获取token",
    responses=login_responses
)
async def refresh(payload: PayloadData = Depends(signature_authentication), cache_client: CacheSystem = Depends(cache)):
    """
    刷新 token
    \f
    :param payload: token 负载数据
    :param cache_client: 缓存客户端
    :return:
    """
    response_data = Token()
    logger.debug(f"刷新token，payload为：{payload}")

    if payload.sub == TokenType.REFRESH_TOKEN.value:
        # 获取token
        access_token, refresh_token = await generate_token(
            username=payload.data.username,
            password='',
            remember=payload.data.remember,
            scopes=','.join(payload.scopes),
            is_refresh=True,
            token_type=payload.data.type,
            request_id=payload.jti
        )
        logger.debug(f"刷新token，获取到的token为：{access_token}")

        # 获取到说明一切正常
        if access_token:
            response_data.access_token = access_token
            response_data.refresh_token = refresh_token
            return response_data
        logger.error(f"刷新token失败，获取到的token为空，payload为：{payload}")
    response = BaseResponse(code=StatusMap.IDENTIFY_INVALID.code, message=StatusMap.IDENTIFY_INVALID.message, success=StatusMap.IDENTIFY_INVALID.success)
    raise SiteException(status_code=StatusMap.IDENTIFY_INVALID.http_code, response=response)


@router.post(
    "/logout",
    response_model=BaseResponse,
    summary="登出接口，清除token"
)
async def logout(payload: PayloadData = Depends(optional_signature_authentication), cache_client: CacheSystem = Depends(cache)):
    """
    登出接口，清除token
    \f
    :param payload: token 负载数据
    :param cache_client: 缓存客户端
    :return:
    """
    if payload.data:
        expire = payload.exp - timegm(datetime.now().utctimetuple())
        await cache_client.set_blacklist(identify=payload.data.id, value=payload.model_dump_json(), expire=expire)
        await cache_client.delete_permission(identify=payload.data.id)
    return BaseResponse()


@router.post("/verify", summary="确认token是否可用", response_model=UserInfoResponse)
async def get_user_info_handler(payload: PayloadData = Depends(signature_authentication)):
    """
    确认token是否可用，同时也可以作为获取用户信息的接口
    \f
    :param payload: token 负载数据
    :return:
    """
    user_info = payload.data
    return UserInfoResponse(data=user_info)


# 生成系统token
@router.post("/system/token", response_model=GenericBaseResponse[Token], summary="生成系统token")
async def system_token(body: SystemTokenBody, payload: PayloadData = Depends(signature_authentication)):
    """
    生成系统token
    \f
    :param body: token参数
    :param payload: token 负载数据
    :return:
    """
    if payload.data.superuser is False:
        response = BaseResponse(code=StatusMap.FORBIDDEN.code, message=StatusMap.FORBIDDEN.message, success=StatusMap.FORBIDDEN.success)
        raise SiteException(status_code=StatusMap.FORBIDDEN.http_code, response=response)
    userinfo = PayloadDataUserInfo(id=body.app_id, name=body.name, username=body.username, email=body.email, avatar=body.avatar, superuser=body.superuser)
    if body.scope:
        scopes = json.loads(body.scope)
    else:
        scopes = list()

    # 如果传入了超时时间，并且大于0，则使用传入值，否则统一为3年期
    if body.exp and body.exp > 0:
        expire = timedelta(seconds=body.exp)
    else:
        expire = timedelta(days=3 * 365)

    access_token = await create_access_token(payload=PayloadData(scopes=scopes, data=userinfo, aud=body.username), expires_delta=expire, subject=TokenType.PROJECT_TOKEN)
    response = BaseResponse(data=Token(access_token=access_token).model_dump())
    return response
